package com.njcb.ams.support.security.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.njcb.ams.factory.comm.DataBus;
import com.njcb.ams.factory.domain.AppContext;
import com.njcb.ams.pojo.dto.standard.Response;
import com.njcb.ams.service.SsoClientService;
import com.njcb.ams.support.config.AmsConfigUtil;
import com.njcb.ams.support.security.bo.LoginModel;
import com.njcb.ams.support.security.config.AmsSecurityConfiguration;
import com.njcb.ams.util.AmsAssert;
import com.njcb.ams.util.AmsJsonUtils;
import com.njcb.ams.util.AmsUtils;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.Assert;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Map;

/**
 * 支持rest登录(json)和表单登录及授权码(authcode)登录的AuthenticationFilter
 * 
 * @author liuyanlong
 */
public class CustomAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

	// ~ Static fields/initializers
	// =====================================================================================

	public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
	public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";

	private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
	private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
	private boolean postOnly = true;

	// ~ Constructors
	// ===================================================================================================

	public CustomAuthenticationFilter() {
		super(new AntPathRequestMatcher("/login"));
	}

	@Override
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
		SsoClientService ssoClientService = AppContext.getBean(SsoClientService.class);
		try {
			Boolean isSucc = ssoClientService.ssoHandle(request,response);
			if(isSucc){
				super.doFilter(req,res,chain);
			}else{
				ssoClientService.ssoRedirect(request,response);
			}
		}catch (Exception e){
			e.printStackTrace();
			PrintWriter out = response.getWriter();
			out.write(AmsJsonUtils.objectToJson(Response.buildFail(e.getMessage())));
			out.flush();
			out.close();
		}
	}

	// ~ Methods
	// ========================================================================================================

	@Override
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
			throws AuthenticationException {

		AmsSecurityConfiguration securityConfig = AmsConfigUtil.getBean(AmsSecurityConfiguration.class);

		/**
		 * 支持授权码时登录
		 */
		if (LoginModel.AUTHCODE.equals(securityConfig.loginModel())) {
			Object authcode = request.getHeader("authcode");
			UsernamePasswordAuthenticationToken authRequest = null;
			if (AmsUtils.isNotNull(authcode)) {
				authRequest = new UsernamePasswordAuthenticationToken(authcode.toString(), authcode.toString());
				setDetails(request, authRequest);
			} else {
				ObjectMapper mapper = new ObjectMapper();
				try (InputStream is = request.getInputStream()) {
					@SuppressWarnings("unchecked")
					Map<String, String> authenticationBean = mapper.readValue(is, Map.class);
					authenticationBean.forEach((key, value) -> {
						DataBus.setAttribute(key, value);
					});
					String bodyauthcode = authenticationBean.get("authcode");
					AmsAssert.notNull(bodyauthcode, "授权码不能为空");
					authRequest = new UsernamePasswordAuthenticationToken(bodyauthcode, bodyauthcode);
					setDetails(request, authRequest);
				} catch (Exception e) {
					throw new AuthenticationServiceException(e.getMessage());
				}
			}
			return this.getAuthenticationManager().authenticate(authRequest);
		}
		/**
		 * 支持ContentType=application/json时登录
		 */
		else if (MediaType.APPLICATION_JSON_UTF8_VALUE.equals(request.getContentType())
				|| MediaType.APPLICATION_JSON_UTF8_VALUE.toLowerCase().equals(request.getContentType().toLowerCase())
				|| MediaType.APPLICATION_JSON_VALUE.equals(request.getContentType())) {
			ObjectMapper mapper = new ObjectMapper();
			UsernamePasswordAuthenticationToken authRequest = null;
			try (InputStream is = request.getInputStream()) {
				@SuppressWarnings("unchecked")
				Map<String, String> authenticationBean = mapper.readValue(is, Map.class);
				String username = authenticationBean.get("username");
				if(AmsUtils.isNull(username)){
					username = "";
				}
				String password = authenticationBean.get("password");
				if(AmsUtils.isNull(password)){
					password = "";
				}
				authenticationBean.forEach((key, value) -> {
					DataBus.setAttribute(key, value);
				});
				authRequest = new UsernamePasswordAuthenticationToken(username, password);
				setDetails(request, authRequest);
			} catch (Exception e) {
				e.printStackTrace();
				throw new AuthenticationServiceException(e.getMessage());
			}
			return this.getAuthenticationManager().authenticate(authRequest);
		}
		/**
		 * 支持ContentType为x-www-form-urlencoded时登录
		 */
		else {
			String username = obtainUsername(request);
			if(AmsUtils.isNull(username)){
				username = "";
			}
			String password = obtainPassword(request);
			if(AmsUtils.isNull(password)){
				password = "";
			}
			request.getParameterMap().forEach((key, value) -> {
				DataBus.setAttribute(key, value);
			});

			username = username.trim();
			UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username,
					password);
			setDetails(request, authRequest);
			return this.getAuthenticationManager().authenticate(authRequest);
		}

	}

	/**
	 * Enables subclasses to override the composition of the password, such as
	 * by including additional values and a separator.
	 * <p>
	 * This might be used for example if a postcode/zipcode was required in
	 * addition to the password. A delimiter such as a pipe (|) should be used
	 * to separate the password and extended value(s). The
	 * <code>AuthenticationDao</code> will need to generate the expected
	 * password in a corresponding manner.
	 * </p>
	 *
	 * @param request
	 *            so that request attributes can be retrieved
	 *
	 * @return the password that will be presented in the
	 *         <code>Authentication</code> request token to the
	 *         <code>AuthenticationManager</code>
	 */
	protected String obtainPassword(HttpServletRequest request) {
		return request.getParameter(passwordParameter);
	}

	/**
	 * Enables subclasses to override the composition of the username, such as
	 * by including additional values and a separator.
	 *
	 * @param request
	 *            so that request attributes can be retrieved
	 *
	 * @return the username that will be presented in the
	 *         <code>Authentication</code> request token to the
	 *         <code>AuthenticationManager</code>
	 */
	protected String obtainUsername(HttpServletRequest request) {
		return request.getParameter(usernameParameter);
	}

	/**
	 * Provided so that subclasses may configure what is put into the
	 * authentication request's details property.
	 *
	 * @param request
	 *            that an authentication request is being created for
	 * @param authRequest
	 *            the authentication request object that should have its details
	 *            set
	 */
	protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
		authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
	}

	/**
	 * Sets the parameter name which will be used to obtain the username from
	 * the login request.
	 *
	 * @param usernameParameter
	 *            the parameter name. Defaults to "username".
	 */
	public void setUsernameParameter(String usernameParameter) {
		Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
		this.usernameParameter = usernameParameter;
	}

	/**
	 * Sets the parameter name which will be used to obtain the password from
	 * the login request..
	 *
	 * @param passwordParameter
	 *            the parameter name. Defaults to "password".
	 */
	public void setPasswordParameter(String passwordParameter) {
		Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
		this.passwordParameter = passwordParameter;
	}

	/**
	 * Defines whether only HTTP POST requests will be allowed by this filter.
	 * If set to true, and an authentication request is received which is not a
	 * POST request, an exception will be raised immediately and authentication
	 * will not be attempted. The <tt>unsuccessfulAuthentication()</tt> method
	 * will be called as if handling a failed authentication.
	 * <p>
	 * Defaults to <tt>true</tt> but may be overridden by subclasses.
	 */
	public void setPostOnly(boolean postOnly) {
		this.postOnly = postOnly;
	}

	public final String getUsernameParameter() {
		return usernameParameter;
	}

	public final String getPasswordParameter() {
		return passwordParameter;
	}

	// @Override
	// public Authentication attemptAuthentication(HttpServletRequest request,
	// HttpServletResponse response) throws AuthenticationException {}
}